

#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <netio/netio.h>
#include <tmc/alloc.h>
#include <tmc/cpus.h>
#include <tmc/sync.h>
#include <tmc/task.h>

#include "mch.h"
#include "lwip/inet.h"
#include "lwip/tcp.h"
#include "lwip/ip_frag.h"
#include "lwip/netif.h"
#include "lwip/init.h"
#include "lwip/stats.h"
#include "netif/etharp.h"
#include "lwip/timers.h"
#include "tile_if.h"


extern int server_init(struct ip_addr *ipaddr, u16_t port);

struct timeval starttime;

static int ip_setting[4] = {10, 0, 0, 200};
static int gw_setting[4] = {10, 0, 0, 1};
static int nm_setting[4] = { 255, 255, 255, 0};

static char *interface_setting = "gbe/1";


static int work_size = 40;
int work_rank;

// Pointer to a barrier
// Shared by all worker processes to do synchronization
static tmc_sync_barrier_t *work_barrier;



u32_t sys_now(void)
{
    struct timeval tv;
    u32_t sec, usec, msec;
    gettimeofday(&tv, NULL);

    sec = (u32_t)(tv.tv_sec - starttime.tv_sec);
    if (tv.tv_usec >= starttime.tv_usec)
        usec = (u32_t)(tv.tv_usec - starttime.tv_usec);
    else
    {
        sec--;
        usec = (u32_t)(tv.tv_usec + 1000000 - starttime.tv_usec);
    }
    msec = sec * 1000 + usec / 1000;
    return msec;
}

static int
fork_processes(int count, cpu_set_t *cpus)
{
    for (int i = 1; i < count; i++)
    {
        pid_t child = fork();
        if (child < 0)
            tmc_task_die("Failure in 'fork()'.");

        if (child == 0)
        {
            // We're a child; return our rank.
            return i;
        }
    }

    // The parent always returns rank 0.
    return 0;
}

static int
go_parallel(int count, cpu_set_t *cpus)
{
    // Start count processes; returns the process rank in each process.
    int rank = fork_processes(count, cpus);

    // Bind each process to the rank'th core in the cpu set.
    if (tmc_cpus_set_my_cpu(tmc_cpus_find_nth_cpu(cpus, rank)) < 0)
        tmc_task_die("Failure in 'tmc_cpus_set_my_cpu()'.");

    return rank;
}

static char
*shift_option(char ***arglist, const char *option)
{
  char **args = *arglist;
  char *first = args[0], **rest = &args[1];

  int optlen = strlen(option);
  char *val = first + optlen;

  if (option[optlen-1] != '=')
  {

    // Simple option without operand.
    //
    if (strcmp(first, option))
      return NULL;
  }
  else
  {
    // Option with operand.
    //
    if (strncmp(first, option, optlen - 1))
      return NULL;

    // Look for operand attached or in next argument.
    //
    if (first[optlen - 1] == '\0')
      val = *rest++;
    else if (first[optlen - 1] != '=')
      return NULL;
  }

  // Update argument list.
  //
  *arglist = rest;
  return val;
}


int main(int argc, char** argv)
{
    cpu_set_t cpus;

    ip_addr_t ipaddr, netmask, gw;
    struct netif netif;


  char **args = &argv[1];
  while (*args)
  {
    char *opt = NULL;

    if ((opt = shift_option(&args, "--workers=")))
      work_size = atoi(opt);

    else
      tmc_task_die("Unknown option '%s'.\n", args[0]);
  }


    if (tmc_cpus_get_my_affinity(&cpus) != 0)
    {
        tmc_task_die("Failure in 'tmc_cpus_get_my_affinity()'.");
    }

    if (tmc_cpus_count(&cpus) < 1)
    {
        tmc_task_die("Insufficient cpus available.");
    }

    tmc_alloc_t alloc = TMC_ALLOC_INIT;
    tmc_alloc_set_shared(&alloc);
    work_barrier = (tmc_sync_barrier_t *)tmc_alloc_map(&alloc, sizeof(*work_barrier));
    if (work_barrier == NULL)
        tmc_task_die("Failed to allocate memory for barrier.");
    tmc_sync_barrier_init(work_barrier, work_size);


    work_rank = go_parallel(work_size, &cpus);

    IP4_ADDR(&gw, gw_setting[0], gw_setting[1], gw_setting[2], gw_setting[3]);
    IP4_ADDR(&ipaddr, ip_setting[0], ip_setting[1], ip_setting[2], ip_setting[3]);
    IP4_ADDR(&netmask, nm_setting[0], nm_setting[1], nm_setting[2], nm_setting[3]);
    gettimeofday(&starttime, NULL);

    mch_printf("TCP server start[%d]...\n", work_rank);

    lwip_init();

    strncpy(netif.netio_ifname, interface_setting, 8);
    netif.worker_id = work_rank;

    tmc_sync_barrier_wait(work_barrier);


    for (int i = 0; i < work_size; i++)
    {
        if (i == work_rank)
        {
            if(NULL==netif_add(&netif, &ipaddr, &netmask, &gw, NULL, tile_if_init, ethernet_input))
                tmc_task_die("Failed to init netif.");
        }
        tmc_sync_barrier_wait(work_barrier);
    }

    if (netif.worker_id == 0)
    {
        tile_if_setup_flow(&netif, work_size);
    }

    tmc_sync_barrier_wait(work_barrier);

    if (netif.worker_id == 0)
    {
        tile_if_start(&netif);
    }

    netif_set_default(&netif);
    netif_set_up(&netif);

    server_init(IP_ADDR_ANY, 80);

    while (1)
    {
        tile_if_input(&netif);
        sys_check_timeouts();

    }
}
